using System;
using System.Reflection;
using System.Xml;
using System.Collections.Generic;

namespace Dida
{
/*	internal class StateMachineLoader<GlueTrait>
		where GlueTrait : StateGlueTrait
	{
		string name;

		ScopedSettingsLoader settingsLoader = new ScopedSettingsLoader();
		Dictionary<string, StateLoader<GlueTrait> > stateLoaders =
			new Dictionary<string, StateLoader<GlueTrait> >();

		StateMachineTemplate<GlueTrait> template;

		public StateMachineLoader(XmlReader reader)
		{
			ReadXml(reader);
			ResolveSettings();
			PropagateTransitions();
			CreateTemplate();
		}

		private void ReadXml(XmlReader reader)
		{
			reader.MoveToContent();

			name = reader.GetAttribute("Name");
			if (name == null)
				throw new XmlException("No Name attribute specified");

			bool needsCloseElement = !reader.IsEmptyElement;
			reader.ReadStartElement();

			while (reader.NodeType == XmlNodeType.Element)
			{
				switch (reader.Name)
				{
					case "Settings":
						settingsLoader.LoadIntoCurScope(reader);
						break;

					case "State":
						ReadState(reader);
						break;

					default:
						throw new SMLInvalidTag(reader.Name, reader);
				}
			}

			if (needsCloseElement)
				reader.ReadEndElement();
		}

		private void ReadState(XmlReader reader)
		{
			string name = reader.GetAttribute("Name");
			if (name == null)
				throw new StateMachineLoaderException("No Name attribute found", reader);

			StateLoader<GlueTrait> stateLoader = CreateStateLoader(name,
				new XmlPositionMarker(reader));
			stateLoader.LoadState(reader,settingsLoader);
		}

		private StateLoader<GlueTrait> CreateStateLoader(string name,XmlPositionMarker errorMarker)
		{
			if (stateLoaders.ContainsKey(name))
			{
				throw new XmlReaderException("There's already a state called \""
					+ name + "\"", errorMarker);
			}

			StateLoader<GlueTrait> ret = new StateLoader<GlueTrait>(this,name);
			stateLoaders[name] = ret;
			return ret;
		}

		internal StateLoader<GlueTrait> GetStateLoader(string name,
			XmlPositionMarker errorPosMarker)
		{
			StateLoader<GlueTrait> ret;
			if (stateLoaders.TryGetValue(name, out ret))
				return ret;
			else
			{
				throw new XmlReaderException("There's no state called \"" +
					name + "\"", errorPosMarker);
			}
		}

		private void ResolveSettings()
		{
			foreach(StateLoader<GlueTrait> state in stateLoaders.Values)
			{
				state.ResolveSettings(settingsLoader);
			}

			List<Exception> errList = settingsLoader.UnusedSettingsErrorList;
			if (errList.Count != 0)
			{
				throw new Exception("kolere");
			}
		}

		private void PropagateTransitions()
		{
			foreach (StateLoader<GlueTrait> state in stateLoaders.Values)
				state.PropagateTransitions();
		}

		private void CreateTemplate()
		{
			template = new StateMachineTemplate<GlueTrait>();

			SettingsContainer[] settingsContainers = new SettingsContainer[
				settingsLoader.FlattendSettingsList.Count];
			for(int i = 0;i < settingsLoader.FlattendSettingsList.Count;i++)
			{
				settingsContainers[i] = settingsLoader.
					FlattendSettingsList[i].settingsContainer;
			}
			template.SetSettingsContainers(settingsContainers);

			foreach (StateLoader<GlueTrait> stateLoader in stateLoaders.Values)
			{
				StateTemplate<GlueTrait> stateTemplate = stateLoader.CreateTemplate();
				template.AddTemplate(stateLoader.Name, stateTemplate);
			}
		}

		public string Name
		{
			get { return name; }
		}

		/// <summary>
		/// Gets the StateMachineTemplate that was loaded using this class.
		/// </summary>
		/// <remarks>
		/// This is the main output value of the StateMachineLoader class.
		/// </remarks>
		public StateMachineTemplate<GlueTrait> Template
		{
			get { return template; }
		}
	}

	internal class StateLoader<BackendTrait>
		where BackendTrait : StateGlueTrait
	{
		StateMachineLoader<BackendTrait> stateMachine;

		string name;
		Dictionary<string, TraitLoader<BackendTrait>> traits = new Dictionary<string, TraitLoader<BackendTrait>>();
		Dictionary<string, EntryTransitionTraitLoader<BackendTrait>> entryTransitions = new Dictionary<string, EntryTransitionTraitLoader<BackendTrait>>();
		Dictionary<string, ExitTransitionTraitLoader<BackendTrait>> exitTransitions = new Dictionary<string, ExitTransitionTraitLoader<BackendTrait>>();
		SortedSet<string> usedNames = new SortedSet<string>();

		// entry transitions are traits that are defined in the state they 
		// are transitioning to, but will be part of the "from" state's trait class.
		// For example, the JumpOnButtonPress transition is part of the jump 
		// state, but is added to the traitclass of the WalkState .
		//
		// This list contains traits that are defined in a different state,
		// but should be added to the traitclass of this state.
		List<EntryTransitionTraitLoader<BackendTrait>> externalTransitions =
			new List<EntryTransitionTraitLoader<BackendTrait>>();

		public StateLoader(StateMachineLoader<BackendTrait> stateMachine,string name)
		{
			this.stateMachine = stateMachine;
			this.name = name;
		}

		public void LoadState(XmlReader reader, ScopedSettingsLoader settingsLoader)
		{
			reader.ReadStartElement();
			settingsLoader.PushScope(name);

			while (reader.NodeType == XmlNodeType.Element)
			{
				switch (reader.Name)
				{
					case "Settings":
						settingsLoader.LoadIntoCurScope(reader);
						break;

					case "Traits":
						LoadTraits(reader,settingsLoader);
						break;

					case "EntryTransitions":
						LoadEntryTransitions(reader,settingsLoader);
						break;

					case "ExitTransitions":
						LoadExitTransitions(reader, settingsLoader);
						break;
				}
			}

			settingsLoader.PopScope();
			reader.ReadEndElement();
		}

		private void LoadTraits(XmlReader reader, ScopedSettingsLoader settingsLoader)
		{
			if (reader.IsEmptyElement)
			{
				reader.ReadStartElement();
				return;
			}

			reader.ReadStartElement();

			while (reader.NodeType == XmlNodeType.Element)
			{
				string name = ReadOrMakeName(reader);
				TraitLoader<BackendTrait> trait = new TraitLoader<BackendTrait>(
					name, reader, settingsLoader);
				traits.Add(trait.Name, trait);
				usedNames.Add(trait.Name);
			}

			reader.ReadEndElement();
		}

		private void LoadEntryTransitions(XmlReader reader,
			ScopedSettingsLoader settingsLoader)
		{
			if (reader.IsEmptyElement)
			{
				reader.ReadStartElement();
				return;
			}

			reader.ReadStartElement();
			while (reader.NodeType == XmlNodeType.Element)
			{
				string name = ReadOrMakeName(reader);
				EntryTransitionTraitLoader<BackendTrait> trait = 
					new EntryTransitionTraitLoader<BackendTrait>(
					name, reader, settingsLoader);
				entryTransitions.Add(trait.Name, trait);
				usedNames.Add(trait.Name);
			}
			reader.ReadEndElement();
		}

		private void LoadExitTransitions(XmlReader reader,
			ScopedSettingsLoader settingsLoader)
		{
			if (reader.IsEmptyElement)
			{
				reader.ReadStartElement();
				return;
			}

			reader.ReadStartElement();
			while (reader.NodeType == XmlNodeType.Element)
			{
				string name = ReadOrMakeName(reader);
				ExitTransitionTraitLoader<BackendTrait> trait =
					new ExitTransitionTraitLoader<BackendTrait>(
					name, reader, settingsLoader);
				exitTransitions.Add(trait.Name, trait);
				usedNames.Add(trait.Name);
			}
			reader.ReadEndElement();
		}

		private string ReadOrMakeName(XmlReader reader)
		{
			string name = reader.GetAttribute("Name");
			if (name != null)
			{
				if (usedNames.Contains(name))
				{
					throw new StateMachineLoaderException("The name \"" + name +
						"\" is already in use", reader);
				}
				else
					return name;
			}
			else
			{
				// no name attribute is specified, so use the element name, followed
				// by an index.
				string baseName = reader.Name;

				int index = 0;
				do
				{
					name = baseName + "_" + index.ToString();
				}
				while (usedNames.Contains(name));

				return name;
			}
		}

		public void ResolveSettings(ScopedSettingsLoader settingsLoader)
		{
			settingsLoader.PushScope(name);

			foreach (TraitLoader<BackendTrait> trait in traits.Values)
				trait.ResolveSettings(settingsLoader);
			foreach (EntryTransitionTraitLoader<BackendTrait> transition in entryTransitions.Values)
				transition.ResolveSettings(settingsLoader);
			foreach (ExitTransitionTraitLoader<BackendTrait> transition in exitTransitions.Values)
				transition.ResolveSettings(settingsLoader);

			settingsLoader.PopScope();
		}

		public void PropagateTransitions()
		{
			foreach (EntryTransitionTraitLoader<BackendTrait> transition in 
				entryTransitions.Values)
			{
				StateLoader<BackendTrait> fromState = 
					stateMachine.GetStateLoader(transition.FromState,
					transition.FromStateXmlPos);

				fromState.externalTransitions.Add(transition);
			}
		}

		public StateTemplate<BackendTrait> CreateTemplate()
		{
			TraitClassBuilder traitClass = new TraitClassBuilder(
				stateMachine.Name + "_" + name);

			List<StateTemplate<BackendTrait>.SettingsContainerInfo> settingsList = 
				new List<StateTemplate<BackendTrait>.SettingsContainerInfo>();

			traitClass.AddTrait(typeof(BackendTrait));

			foreach (TraitLoader<BackendTrait> trait in traits.Values)
			{
				int traitIndex = traitClass.AddTrait(trait.TraitType);
				trait.AddSettingsToList(settingsList,traitIndex);
			}

			foreach (ExitTransitionTraitLoader<BackendTrait> trait in
				exitTransitions.Values)
			{
				int traitIndex = traitClass.AddTrait(trait.TraitType);
				trait.AddSettingsToList(settingsList,traitIndex);
			}

			foreach (EntryTransitionTraitLoader<BackendTrait> trait 
				in externalTransitions)
			{
				int traitIndex = traitClass.AddTrait(trait.TraitType);
				trait.AddSettingsToList(settingsList,traitIndex);
			}

			TraitClass compiledTraitClass = traitClass.Compile();
		
			StateTemplate<BackendTrait> ret = new StateTemplate<BackendTrait>(
				compiledTraitClass);
			ret.SetSettings(settingsList);
			return ret;
		}

		public string Name
		{
			get { return name; }
		}
	}

	internal class TraitLoader<BackendTrait>
		where BackendTrait : StateGlueTrait
	{
		string name;
		Type type;

		struct SettingsField
		{
			public FieldInfo field;
			public ScopedSettingsLoader.SettingsContainerInfo settings;
		}
		List<SettingsField> settingsFields = new List<SettingsField>();

		protected TraitLoader() { }

		protected void Create(string name, XmlReader reader,
			ScopedSettingsLoader settingsLoader)
		{
			this.name = name;

			string typeName = reader.Name;
			type = TraitResolver<BackendTrait>.ResolveTrait(typeName, reader);

			settingsLoader.PushScope(name);

			if (reader.IsEmptyElement)
				reader.ReadStartElement();
			else
			{
				reader.ReadStartElement();
				while (reader.NodeType == XmlNodeType.Element)
				{
					if (reader.Name == "Settings")
					{
						settingsLoader.LoadIntoCurScope(reader);
					}
					else
						throw new SMLInvalidTag(reader.Name, reader);
				}
				reader.ReadEndElement();
			}

			settingsLoader.PopScope();
		}

		public TraitLoader(string name, XmlReader reader,
			ScopedSettingsLoader settingsLoader)
		{
			Create(name, reader, settingsLoader);			
		}

		public string Name
		{
			get { return name; }
		}

		public Type TraitType
		{
			get { return type; }
		}

		public void ResolveSettings(ScopedSettingsLoader settingsLoader)
		{
			settingsLoader.PushScope(name);

			FieldInfo[] fields = type.GetFields(BindingFlags.Instance|
				BindingFlags.Public|BindingFlags.NonPublic);
			foreach (FieldInfo field in fields)
			{
				if (field.GetCustomAttributes(typeof(FromSettingsAttribute),
					true).Length != 0)
				{
					ScopedSettingsLoader.SettingsContainerInfo settings = 
						settingsLoader.GetSettings(field.FieldType);

					SettingsField settingsField = new SettingsField();
					settingsField.field = field;
					settingsField.settings = settings;
					settingsFields.Add(settingsField);
				}
			}

			settingsLoader.PopScope();
		}

		public void AddSettingsToList(List<StateTemplate<BackendTrait>.SettingsContainerInfo> settingsList,int traitIndex)
		{
			foreach (SettingsField settingsField in settingsFields)
			{
				StateTemplate<BackendTrait>.SettingsContainerInfo curSettings = 
					new StateTemplate<BackendTrait>.SettingsContainerInfo();
				curSettings.containerIndex = settingsField.settings.index;
				curSettings.member = settingsField.field;
				curSettings.traitIndex = traitIndex;

				settingsList.Add(curSettings);
			}
		}
	}

	internal class EntryTransitionTraitLoader<BackendTrait> : TraitLoader<BackendTrait>
		where BackendTrait : StateGlueTrait
	{
		string fromState;
		XmlPositionMarker fromStateXmlPos;

		public EntryTransitionTraitLoader(string name, XmlReader reader,
			ScopedSettingsLoader settingsLoader)
		{
			reader.MoveToAttribute("From");
			fromStateXmlPos = new XmlPositionMarker(reader);
			fromState = reader.Value;
			reader.MoveToElement();

			if (fromState == null)
				throw new StateMachineLoaderException("No \"From\" attribute specified", reader);

			// call the "constructor" method of the base class
			Create(name, reader, settingsLoader);

			if(!TraitType.IsSubclassOf(typeof(StateEntryTransition<BackendTrait>)))
			{
				throw new StateMachineLoaderException("The trait " + TraitType.Name +
					" isn't an entry transition (it isn't derived from " + 
					"StateEntryTransition<> class)",reader);
			}
		}

		public string FromState
		{
			get { return fromState; }
		}

		public XmlPositionMarker FromStateXmlPos
		{
			get { return fromStateXmlPos; }
		}
	}

	internal class ExitTransitionTraitLoader<BackendTrait> : TraitLoader<BackendTrait>
		where BackendTrait : StateGlueTrait
	{
		string toState;

		public ExitTransitionTraitLoader(string name, XmlReader reader,
			ScopedSettingsLoader settingsLoader)
		{
			toState = reader.GetAttribute("To");
			if (toState == null)
				throw new StateMachineLoaderException("No \"To\" attribute specified", reader);

			// call the "constructor" method of the base class
			Create(name, reader, settingsLoader);

			if (!TraitType.IsSubclassOf(typeof(StateExitTransition<BackendTrait>)))
			{
				throw new StateMachineLoaderException("The trait " + TraitType.Name +
					" isn't an exit transition (it isn't derived from " +
					"StateExitTransition<> class)", reader);
			}
		}
	}

	public static class TraitResolver<BackendTrait>
		where BackendTrait : StateGlueTrait
	{
		public static Type ResolveTrait(string name,XmlReader reader)
		{
			switch (name)
			{
				case "JumpController":
					return typeof(Library.JumpController);
				case "JumpOnButtonPress":
					return typeof(Library.JumpOnButtonPress);
				case "WalkController":
					return typeof(Library.WalkStateController);
			}

			throw new StateMachineLoaderException("Trait not found: " + name,reader);
		}
	}

	public class StateMachineLoaderException : Exception
	{
		private string msg;
		private int line = 0;
		private int pos = 0;

		public StateMachineLoaderException(string msg, XmlReader reader)
		{
			this.msg = msg;

			if (reader is IXmlLineInfo)
			{
				IXmlLineInfo lineInfo = (IXmlLineInfo)reader;
				line = lineInfo.LineNumber;
				pos = lineInfo.LinePosition;
			}
		}

		public override string Message
		{
			get
			{
				return "Error in statemachine definition, at line " + line.ToString()
					+ ", position " + pos.ToString() + ": " + msg;
			}
		}
	}

	/// <summary>
	/// Abbreviation for StateMachineLoaderInvalidTagException. This exception
	/// is thrown when an invalid tag is found in the xml.
	/// </summary>
	public class SMLInvalidTag : StateMachineLoaderException
	{
		public SMLInvalidTag(string name, XmlReader reader)
			: base(name + " is an invalid element.", reader)
		{
		}
	}*/
}
